package com.wg.core.lock;

import cn.hutool.core.util.IdUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.wg.db.redis.Redis;
import redis.clients.jedis.Jedis;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Redis分布式锁实现（优化版）
 *
 * <p>特性：
 * 1. 支持多键锁（按字典序排序避免死锁）
 * 2. 原子性加锁（SET NX EX）
 * 3. 锁续期机制（看门狗）
 * 4. 安全的锁释放（Lua脚本保证原子性）
 * 5. 可重入锁支持
 * 6. 锁超时自动释放</p>
 *
 * @author 少爷123
 */
public class RedisLock {
    private static final Log logger = LogFactory.get();
    private static final String LOCK_SUCCESS = "OK";
    private static final String UNLOCK_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "   return redis.call('del', KEYS[1]) " +
                    "else " +
                    "   return 0 " +
                    "end";

    private final Redis redis;
    private final String[] keys;
    private final String lockId;
    private final List<String> lockKeys = new LinkedList<>();

    private int expireSeconds;
    private int retrySeconds;
    private volatile boolean locked = false;
    private Thread watchDogThread;

    /**
     * 创建Redis锁实例
     *
     * @param redis         Redis连接
     * @param expireSeconds 锁过期时间（秒）
     * @param retrySeconds  获取锁重试时间（秒）
     * @param keys          要锁定的键
     */
    public RedisLock(Redis redis, int expireSeconds, int retrySeconds, String... keys) {
        this.redis = redis;
        this.keys = keys;
        this.expireSeconds = expireSeconds;
        this.retrySeconds = retrySeconds;
        this.lockId = IdUtil.fastSimpleUUID(); // 生成唯一锁标识
    }

    public int getExpireSeconds() {
        return expireSeconds;
    }

    public void setExpireSeconds(int expireSeconds) {
        this.expireSeconds = expireSeconds;
    }

    public int getRetrySeconds() {
        return retrySeconds;
    }

    public void setRetrySeconds(int retrySeconds) {
        this.retrySeconds = retrySeconds;
    }

    public String[] getKeys() {
        return keys;
    }

    public List<String> getLockKeys() {
        return Collections.unmodifiableList(lockKeys);
    }

    public boolean isLocked() {
        return locked;
    }

    /**
     * 尝试获取分布式锁
     *
     * @return true表示成功获取锁
     */
    public boolean acquire() {
        if (locked) {
            return true; // 可重入锁
        }

        Arrays.sort(keys); // 避免死锁：按字典序排序
        lockKeys.clear();

        long startTime = System.currentTimeMillis();
        long maxWaitMillis = TimeUnit.SECONDS.toMillis(retrySeconds);

        try (Jedis jedis = redis.getJedis()) {
            while (true) {
                for (String key : keys) {
                    if (lockKeys.contains(key)) continue;

                    // 原子性加锁：SET key value NX EX seconds
                    String result = jedis.set(key, lockId, "NX", "EX", expireSeconds);

                    if (LOCK_SUCCESS.equals(result)) {
                        lockKeys.add(key);
                    } else {
                        // 检查锁是否属于当前线程（可重入）
                        String currentValue = jedis.get(key);
                        if (lockId.equals(currentValue)) {
                            lockKeys.add(key);
                            continue;
                        }
                        break;
                    }
                }

                // 成功获取所有锁
                if (lockKeys.size() == keys.length) {
                    locked = true;
                    startWatchDog(); // 启动锁续期
                    return true;
                }

                // 超时检查
                if (System.currentTimeMillis() - startTime > maxWaitMillis) {
                    releaseAcquiredLocks(jedis);
                    return false;
                }

                // 退避策略：指数退避
                long elapsed = System.currentTimeMillis() - startTime;
                long backoffTime = Math.min(200, elapsed / 10);
                try {
                    Thread.sleep(backoffTime);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    releaseAcquiredLocks(jedis);
                    return false;
                }
            }
        } catch (Exception e) {
            logger.error("get redis lock exception", e);
            return false;
        }
    }

    /**
     * 释放已获取的部分锁
     */
    private void releaseAcquiredLocks(Jedis jedis) {
        if (lockKeys.isEmpty()) return;

        for (String key : lockKeys) {
            try {
                jedis.eval(UNLOCK_SCRIPT, Collections.singletonList(key), Collections.singletonList(lockId));
            } catch (Exception e) {
                logger.warn("release part lock failed: {}", key, e);
            }
        }
        lockKeys.clear();
    }

    /**
     * 启动看门狗线程（锁续期）
     */
    private void startWatchDog() {
        if (watchDogThread != null) return;

        watchDogThread = new Thread(() -> {
            try {
                while (locked) {
                    Thread.sleep(expireSeconds * 1000 / 3); // 每1/3过期时间续期一次

                    try (Jedis jedis = redis.getJedis()) {
                        for (String key : lockKeys) {
                            // 续期锁：只有锁属于当前线程才续期
                            String currentValue = jedis.get(key);
                            if (lockId.equals(currentValue)) {
                                jedis.expire(key, expireSeconds);
                            } else {
                                locked = false; // 锁已被抢占
                                break;
                            }
                        }
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } catch (Exception e) {
                logger.error("watch dog thread exception", e);
            } finally {
                watchDogThread = null;
            }
        }, "RedisLock-WatchDog-" + lockId);

        watchDogThread.setDaemon(true);
        watchDogThread.start();
    }

    /**
     * 释放分布式锁
     */
    public void release() {
        if (!locked) return;

        locked = false;
        stopWatchDog();

        try (Jedis jedis = redis.getJedis()) {
            for (String key : lockKeys) {
                try {
                    // 使用Lua脚本保证原子性释放
                    jedis.eval(UNLOCK_SCRIPT, Collections.singletonList(key), Collections.singletonList(lockId));
                } catch (Exception e) {
                    logger.warn("release lock failed: {}", key, e);
                }
            }
            lockKeys.clear();
        } catch (Exception e) {
            logger.error("release redis lock exception", e);
        }
    }

    /**
     * 停止看门狗线程
     */
    private void stopWatchDog() {
        if (watchDogThread != null) {
            watchDogThread.interrupt();
            watchDogThread = null;
        }
    }
}